﻿#region Using Statements

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

#endregion


namespace Freeform
{

    #region Viewport2D

    /// <summary>
    /// The viewport class represents a section of the game-screen that a scene can be rendered to.
    /// </summary>
    public class Viewport2D : Component
    {

        #region Members

        /// <summary>
        /// Gets the world that this viewport is displaying.
        /// </summary>
        public virtual World World { get; private set; }


        /// <summary>
        /// Gets the spatialproperties of the viewport. 
        /// The position refers to the topleft of the viewport. 
        /// (Values for position and size should range from 0 to 1 as fractions of screen resolution.)
        /// </summary>
        public virtual SpatialProperties SpatialProperties { get; private set; }


        /// <summary>
        /// Gets or sets whether this viewport is currently visible.
        /// </summary>
        public virtual Boolean IsVisible { get; set; }


        /// <summary>
        /// Gets the rendertarget for this viewport.
        /// </summary>
        public virtual RenderTarget2D RenderTarget { get; private set; }


        /// <summary>
        /// Gets or sets the depth of this viewport (0 = back, 1 = front.)
        /// </summary>
        public virtual Single Depth { get; set; }


        /// <summary>
        /// Gets or sets the colour overlay of this viewport.
        /// </summary>
        public virtual Color ColourOverlay { get; set; }


        /// <summary>
        /// Gets the spritebatch used to render this viewport.
        /// </summary>
        public virtual SpriteBatch SpriteBatch { get; private set; }


        /// <summary>
        /// Gets the camera for this viewport.
        /// </summary>
        public virtual Camera Camera { get; private set; }


        /// <summary>
        /// Gets or sets some text to be rendered to the top left corner of the viewport. (Make sure to define a DebugFont also.)
        /// </summary>
        public virtual String DebugText { get; set; }


        /// <summary>
        /// Gets or sets the font to use to render debugging information.
        /// </summary>
        public virtual SpriteFont DebugFont { get; set; }


        /// <summary>
        /// Gets or sets the colour of the debug text.
        /// </summary>
        public virtual Color DebugColour { get; set; }


        /// <summary>
        /// Gets whether the mouse is over this viewport.
        /// </summary>
        public virtual Boolean IsMouseOver
        {
            get
            {
                Vector2 MouseLoc = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
                Vector2 Resolution = new Vector2(Game.GraphicsDeviceManager.PreferredBackBufferWidth,
                    Game.GraphicsDeviceManager.PreferredBackBufferHeight);
                Vector2 MouseFrac = MouseLoc / Resolution;

                if (MouseFrac.X > SpatialProperties.Position.X && MouseFrac.Y > SpatialProperties.Position.Y &&
                    MouseFrac.X < SpatialProperties.Position.X + SpatialProperties.Size.X &&
                    MouseFrac.Y < SpatialProperties.Position.Y + SpatialProperties.Size.Y)
                    return true;
                return false;
            }
        }


        /// <summary>
        /// Gets the position in World units that the mouse is over, using this viewport.
        /// </summary>
        public virtual Vector2 MouseLocation_World
        {
            get { return Camera.SpatialProperties.Position + Camera.SpatialProperties.Size * MouseLocation_View; }
        }


        /// <summary>
        /// Gets the position of the mouse in Viewport proportions between Vector2.Zero (TopRight of Viewport) and Vector2.One (BottomLeft of Viewport)
        /// </summary>
        public virtual Vector2 MouseLocation_View
        {
            get
            {
                Vector2 MouseLoc = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
                Vector2 Resolution = new Vector2(Game.GraphicsDeviceManager.PreferredBackBufferWidth,
                    Game.GraphicsDeviceManager.PreferredBackBufferHeight);
                return (MouseLoc / Resolution - SpatialProperties.Position) / SpatialProperties.Size;
            }
        }


        /// <summary>
        /// Sets the sorting mode to be used by this viewport when rendering Actors.
        /// </summary>
        public SpriteSortMode SpriteSorting { get; set; }

        #endregion


        #region Init

        /// <summary>
        /// Creates a new 2d viewport.
        /// </summary>
        /// <param name="game">A reference to the game instance.</param>
        public Viewport2D(World world)
            : base(world.Game.ComponentList)
        {
            World = world;
            Camera = new Camera(this);
            SpriteBatch = new SpriteBatch(Game.GraphicsDevice);
            SpatialProperties = new SpatialProperties();
            IsVisible = true;
            Depth = 0.5f;
            ColourOverlay = Color.White;
            RenderTarget = new RenderTarget2D(Game.GraphicsDevice, Game.GraphicsDevice.PresentationParameters.BackBufferWidth,
                Game.GraphicsDevice.PresentationParameters.BackBufferHeight);
            SpriteSorting = SpriteSortMode.BackToFront;
            Game.Viewports.Add(this);
            World.Viewports.Add(this);
        }

        #endregion


        #region Update

        /// <summary>
        /// Allows the viewport to perform any update functions.
        /// </summary>
        /// <param name="time">Provides a snapshot of timing values.</param>
        public override void Update(GameTime time)
        {
            // Apply motion.
            if (SpatialProperties.MotionController != null)
                SpatialProperties.MotionController.Update(time);

            base.Update(time);
        }

        #endregion


        #region Draw

        /// <summary>
        /// Allows the viewport to draw itself.
        /// </summary>
        /// <param name="time"></param>
        public virtual void Draw(GameTime time)
        {
            // Render to the rendertarget for this viewport.
            Game.SetRenderTarget(RenderTarget);

            Game.GraphicsDevice.Clear(World.BackgroundColour);

            // Start the spritebatch.
            SpriteBatch.Begin(SpriteSorting, BlendState.AlphaBlend);

            // Render each entity.
            for (int i = 0; i < World.ComponentList.Loaded.Count; i++)
            {
                Entity Entity = World.ComponentList.Loaded[i] as Entity;
                if (Entity != null && !Entity.Disposed)
                    Entity.Draw(time, this);
            }

            // Render debug text.
            if (DebugText != null && DebugFont != null)
                SpriteBatch.DrawString(DebugFont, DebugText, new Vector2(5, 5), DebugColour);

            // End scene.
            SpriteBatch.End();
        }

        #endregion


        #region Dispose

        /// <summary>
        /// Disposes this viewport and removes it from the game.
        /// </summary>
        protected override void OnDispose()
        {
            base.OnDispose();

            // Remove references from parents.
            Game.Viewports.Remove(this);
            World.Viewports.Remove(this);

            // Dispose rendertarget.
            RenderTarget.Dispose();

            // Dispose spritebatch.
            SpriteBatch.Dispose();

            // Dispose camera.
            Camera.Dispose();
        }

        #endregion

    }

    #endregion


    #region Camera

    /// <summary>
    /// The camera controls where the entities appear in the viewport.
    /// </summary>
    public class Camera : Component
    {

        #region Members

        /// <summary>
        /// Gets the viewport that this camera belongs to.
        /// </summary>
        public virtual Viewport2D Viewport { get; private set; }


        /// <summary>
        /// Gets the world that this camera is observing.
        /// </summary>
        public virtual World World { get; private set; }


        /// <summary>
        /// Gets the spatial properties of the camera.
        /// The position and size are in world coordinates. The position refers to the top left of the camera.
        /// The size governs how much fits in the viewport (the zoom effectively.)
        /// </summary>
        public virtual SpatialProperties SpatialProperties { get; private set; }

        #endregion


        #region Init

        /// <summary>
        /// Creates a new instance of a camera.
        /// </summary>
        /// <param name="viewport">The viewport that this camera belongs to.</param>
        internal Camera(Viewport2D viewport)
            : base(viewport.World.ComponentList)
        {
            Viewport = viewport;
            World = Viewport.World;
            SpatialProperties = new SpatialProperties();
            IsEnabled = true;
        }

        #endregion


        #region Update

        /// <summary>
        /// Allows the camera to update itself.
        /// </summary>
        /// <param name="time">Provides a snapshot of timing values.</param>
        public override void Update(GameTime time)
        {
            if (SpatialProperties.MotionController != null)
                SpatialProperties.MotionController.Update(time);

            base.Update(time);
        }

        #endregion


        #region Utility

        /// <summary>
        /// Sets the centre-point of the camera.
        /// </summary>
        /// <param name="position">The centre-point of the camera.</param>
        public virtual void CentreCamera(Vector2 position)
        {
            SpatialProperties.Position = position - SpatialProperties.Size / 2;
        }


        /// <summary>
        /// Applies zoom to the camera relative to the centre-point.
        /// </summary>
        /// <param name="factor">The zoomfactor. Example: 1.5f --> Zoom in by 50%,  0.5f --> Zoom out by 50%</param>
        public virtual void Zoom(Single factor)
        {
            // Find the centre-point of the screen.
            Vector2 centre = SpatialProperties.Position + SpatialProperties.Size / 2;

            // Apply zoom effect.
            SpatialProperties.Size /= factor;

            // Maintain camera centre.
            CentreCamera(centre);
        }


        /// <summary>
        /// Transforms a point in world-space to pixel-space on the viewport.
        /// </summary>
        /// <param name="position">The point in world-space.</param>
        /// <returns>A point in pixel-space.</returns>
        public virtual Vector2 TransformPosition(Vector2 position)
        {
            return new Vector2((position.X - SpatialProperties.Position.X) * Viewport.RenderTarget.Width / SpatialProperties.Size.X,
                (position.Y - SpatialProperties.Position.Y) * Viewport.RenderTarget.Height / SpatialProperties.Size.Y);
        }


        /// <summary>
        /// Transforms a size in world-space to a size in pixels.
        /// </summary>
        /// <param name="size">The size in world-space.</param>
        /// <returns>The size in pixels.</returns>
        public virtual Vector2 TransformSize(Vector2 size)
        {
            return new Vector2(size.X * Viewport.RenderTarget.Width / SpatialProperties.Size.X,
                size.Y * Viewport.RenderTarget.Height / SpatialProperties.Size.Y);
        }

        #endregion

    }

    #endregion

}